Skip to content

ci: wire typecheck/test/astro-check/gh-pages-build into CI#1

Merged
micahchoo merged 24 commits into
mainfrom
ci/wire-checks
Jul 5, 2026
Merged

ci: wire typecheck/test/astro-check/gh-pages-build into CI#1
micahchoo merged 24 commits into
mainfrom
ci/wire-checks

Conversation

@micahchoo

Copy link
Copy Markdown
Owner

Summary

Implements ISSUES.md Issue 1 — CI was deploy-only and root typecheck was red on main.

  • Repaired every red check locally first, one fix per commit (render-core: 2 branded/union-narrowing
    fixes; render-mount: an addOverlay type mismatch plus ~20 strict-mode test errors; studio: a dead
    duplicate-interface block, an XyzTileSource narrowing cluster, and a handful of one-off fixes —
    see ledgers/GATE.md for the full per-check inventory and commit trail).
  • Added apps/studio/tsconfig.json + a "typecheck" script (studio had none).
  • Added .github/workflows/checks.yml: four independent jobs (typecheck, test, astro-check,
    gh-pages-build) on every push + PRs targeting main. deploy.yml is untouched.
  • Fixed a fresh-clone-only bug found by cloning into /tmp and re-running everything there rather
    than trusting the dirty working copy: apps/viewer's typecheck depends on a gitignored,
    astro-generated .astro/types.d.ts that only existed locally because dev/build had been run here
    before. Added a pretypecheck: astro sync step (matching the existing predev/prebuild convention).

Note: this branch also carries several commits from a concurrent session working ISSUES.md Issue 3
(tracked-artifact cleanup — NUL byte, --output, anti-pattern-report.txt, root dist/ sync) and
one Issue 4 inventory commit, all made to local main while this CI work was in progress. Both were
verified together (full typecheck + test sweep re-run at current HEAD).

Test plan

  • pnpm typecheck green on a genuinely fresh clone (not just the working copy)
  • pnpm test green on a fresh clone (688+116+7+98+63+148 = 1120 tests)
  • pnpm --filter @archie/viewer run check (astro check) green
  • bash scripts/build-gh-pages.sh succeeds
  • Confirm checks.yml runs green on this PR in GitHub Actions
  • Trip each of the 4 jobs red with a planted defect, confirm it's caught, then revert

micahchoo added 15 commits July 5, 2026 00:34
…ct tests

ViewerRoute is a discriminated union — accessing objectId/noteId requires
narrowing on view === "exhibit" first. The W3CAnnotation → Record cast needs
an unknown intermediate since the extension key isn't part of the nominal type.
…erLike and OSD Viewer

@types/openseadragon declares OverlayOptions.element as HTMLElement-only and
location as Point|Rect, but frame-overlay.ts deliberately keeps FrameViewerLike
duck-typed and decoupled from OSD's concrete types (so it stays test-mockable
without importing OSD). Narrow the element type to match OSD's real contract,
cast the SVG element at its one call site (SVG works fine at runtime, OSD's
types just don't say so), and assert Viewer satisfies FrameViewerLike once at
the mount.ts wiring boundary instead of coupling the interface back to OSD.
…tyTypes test errors

Array/NodeList indexing and open-handler lookups return T|undefined under
noUncheckedIndexedAccess; assert non-null with ! at the known-populated
indices (same idiom as existing render-core tests). The innerHTML spy's
PropertyDescriptor.get needs a conditional spread instead of `get: desc?.get`
since exactOptionalPropertyTypes rejects an explicit undefined on an optional
property. render-mount typecheck (`pnpm --filter @render/mount exec tsc
--noEmit`) is now fully green; all 116 tests still pass.
… interfaces

store.ts already re-exported these as type aliases of @render/core's
WorkingObjectMeta/WorkingExhibitMeta/WorkingLibraryMeta (the Q-3 archie-persistence
migration), but the old local interface declarations were never deleted —
same field lists, same shapes, just stale. TS reported them as conflicting
exported declarations, and their remaining RightsFields/MediaType/Section/Reading
references as unresolvable (those imports were dropped when the migration
landed). Re-import the Working* types under local aliases so both the file's
own usages and the re-export contract for external importers (library-meta.svelte.ts,
library-meta-reducers.ts, publish-flows.svelte.ts, LibraryHome.svelte) keep working.
…ull union

geoBasemap, AddMapModal's tileSource, and addMapObject's parameter were all
annotated TileSourceDescriptor (the XyzTileSource | DziTileSource union) even
though a Map is always the xyz variant — the annotation widened the literal
and erased that. geo-notes.ts's geoLabelOf/geoForTarget then failed calling
core's pixelToLngLat/lngLatToPixel, which need Extentish (tileSize+maxZoom,
meaningless for a DZI pyramid). Narrow all four to XyzTileSource: fixes the
typecheck errors and, as a side effect, makes it a type error to ever feed a
DZI descriptor into the geo/Web-Mercator math, which was always nonsensical.
…publish-flows, save-queue)

- binding.ts: FileSystemWritableFileStream.write's chunk type excludes
  SharedArrayBuffer-backed views; fflate's zip stream never produces one, so
  assert Uint8Array<ArrayBuffer> instead of the wrong-generic ArrayBufferView cast.
- collab.test.ts: used AnnotationSession's private `log` where every other line
  in the file already used the public `entries` getter for the same value.
- publish-flows.svelte.ts: LogLookup's real signature takes a plain string
  exhibitId; annotating the three lambdas' parameter as the branded LogicalId
  narrowed it below what the callback type allows (contravariance violation).
- save-queue.svelte.ts: non-null assert the last-message index, already guarded
  by the preceding length check (existing codebase idiom for noUncheckedIndexedAccess).

`pnpm --filter @archie/studio exec tsc --noEmit` is now fully green; all 148
tests still pass.
No generator script for this report exists anywhere in the repo, so a
committed copy can only drift the moment code changes.

Ledger ledgers/ARTIFACTS.md row 2.
addPendingNotes's dedup key (`${p.objectId}\0${p.comment}`) needs a
separator guaranteed absent from real data. It was written as a raw
0x00 byte pasted into the source text instead of the JS escape
sequence — identical at runtime, but it made grep, ripgrep, git grep,
and the fff tools treat the whole 2147-line file as binary and
silently report zero matches on every search.

Ledger ledgers/ARTIFACTS.md row: NUL byte. Verified: file(1) now
reports text, git grep matches inside the file, studio suite green
(148/148).
The tsconfig.json + all the error fixes landed in prior commits, but the
actual "typecheck": "tsc --noEmit" script was never added to package.json —
pnpm -r typecheck silently skipped studio the whole time (it only runs the
script in packages that declare it). Root `pnpm typecheck` now covers all 6
packages.
Root dist/ hand-mirrors packages/archie-viewer/dist/ so jsDelivr's
/gh/ root-relative serving resolves the @v1 embed URL (a656cda). No
build step enforced the two trees stay identical. Adds `pnpm sync-dist`
(resync) and `pnpm sync-dist:check` (verify, exit 1 on drift), and
documents the release rule in ADR-0019 + the README embed recipe.

Ledger ledgers/ARTIFACTS.md row: dist/.
Ledger ledgers/ARTIFACTS.md: all four rows done, zero accident rows
remain. ISSUES.md Issue 3 -> done 2026-07-05.
Runs on every push and on PRs targeting main. Four independent jobs so each
check's pass/fail is visible separately in the PR UI: pnpm typecheck (all 6
packages), pnpm test (per-package vitest via --no-bail), astro check for
apps/viewer, and a gh-pages-build verification (separate from deploy.yml's
actual deploy build — catches build breaks on PRs, where deploy.yml never runs).
Same Node 24 / pnpm 10 / LFS checkout as deploy.yml; deploy.yml itself untouched.
Created during the Issue 1 typecheck repair but never actually staged —
the "typecheck" script added to package.json would have had nothing to run
against on a fresh checkout/CI runner.
…heckout

apps/viewer/tsconfig.json includes .astro/types.d.ts for the astro/client
triple-slash reference that gives import.meta.env its type — but .astro/ is
gitignored (generated) and only existed in local working dirs that had
already run `astro dev`/`build`/`check` at least once. A fresh clone (any
CI runner) had nothing to generate it, so `tsc --noEmit` failed on
published.ts's import.meta.env access with "Property 'env' does not exist
on type 'ImportMeta'" — caught by cloning into /tmp and re-running typecheck
there rather than trusting the dirty working copy. Added a `pretypecheck`
script (matching the existing predev/prebuild convention in this package)
that runs `astro sync` first.
@micahchoo
micahchoo merged commit 045e498 into main Jul 5, 2026
8 checks passed
@micahchoo
micahchoo deleted the ci/wire-checks branch July 5, 2026 08:39
micahchoo added a commit that referenced this pull request Jul 7, 2026
qa/hooks/install.sh points core.hooksPath at qa/hooks, but the four
git-lfs hooks (post-checkout/post-commit/post-merge/pre-push) living
there were untracked. A fresh clone already gets working LFS hooks
auto-installed into .git/hooks by git-lfs itself during checkout --
running install.sh then SILENTLY switches the effective hooksPath away
from that working directory to one missing them, with zero warning.
Tracking these (stock, repo-agnostic git-lfs boilerplate, confirmed
byte-identical to what git-lfs generates) closes the regression: once
core.hooksPath points at qa/hooks, the LFS hooks are there too.

Ledger: ledgers/COLDSTART.md fix #1.
micahchoo added a commit that referenced this pull request Jul 19, 2026
Decision Archie-da38: narrative beats stay authored in the object editor
(ADR-0005/ADR-0016 §56); the overview spine stays read-only. W11's round-trip
friction is fixed by navigation, not a second authoring surface — each spine
row now jumps to the editor at the beat's object with the Narrative panel
scrolled + highlighted on that section, and browser back returns to the
overview for free (an ordinary ADR-0024 place push, no new URL rung).

- App.svelte: openBeat(sectionId) — enters the editor and reuses
  navigateToSection's switchObject+focusSectionId to set the transient
  focus intent (ADR-0024 #1: selected panels/scroll are never in the URL).
- ExhibitOverview.svelte: spine rows call onopenbeat instead of onopenobject;
  a row whose beat object no longer exists (deleteObjectNotesAndMeta drops
  the object but doesn't prune orphaned sections) renders inert (ns-beat-gone)
  instead of linking to a dead target.
- NarrativeEditor.svelte: activeSectionId changes now scroll the target card
  into view, move focus to it (tabindex="-1" + aria-label, so screen readers
  announce it), and play a one-shot outline pulse (geometry, not just color)
  layered on the existing persistent .active border.
micahchoo added a commit that referenced this pull request Jul 19, 2026
…hie-a911)

Durable shape for the section rev-log — the piece the probe explicitly did
not prove (PROBE-structure-revlog sharp edge 5).

- structure-serialize.ts: SectionRecord <-> on-disk JSON, both directions
  carry-sentinel guarded (rule #3). Composed SectionKey strips to the LOCAL
  id at the boundary (sharp edge 4); sectionKey() recomposes on read and
  stays the sole composer (re-asserts containment on untrusted on-disk ids).
- structure-persist.ts: history/{localId}.json pages + history/index.json,
  content pages FIRST / index LAST (rule #1 commit point). Per-page tolerant
  read (rule #2): corrupt page skips-and-reports (StructureCorruptError
  sibling); absent store (no dir / no committed index) reads empty, never
  corrupt, and torn pages never read as complete.
- 15 tests: full author->persist(MemoryFilesystem)->reload->project
  deep-equal vs never-persisted projection (creates, edits, kept tombstone,
  un-delete, resolved conflict, dangling ref); write-order instrument;
  torn-index; missing/torn/wrong-schema/hostile-id pages; per-item skip.

Gates: render-core vitest 901 passed (80 files); tsc --noEmit exit 0;
pnpm -r typecheck exit 0.
micahchoo added a commit that referenced this pull request Jul 19, 2026
…e-aef4)

publishLibrary (and thus libraryToZipFs/libraryToZip and the folder writeTree
path — all sinks share the one projection) now accepts PublishOptions.getStructure
and, for each exhibit whose lookup returns a non-empty section rev-log, writes
{slug}/structure/history/{localId}.json + history/index.json via writeStructure —
the exact layout mergeImportedStructure (Archie-2a9a) reads, unlatching the
zip/folder import merge. Pages-first-index-LAST holds inside the emission (rule #1),
and archie.json stays the LAST write of the whole publish (order test-pinned).

Emission is driven by log EXISTENCE, deliberately NOT by the archie.structureRevlog
flag: a log only exists if the flag was ON while authoring, and a library that HAS
history must carry it regardless of the flag's current position. No log -> no
structure/ dir; the tree is byte-identical to the pre-aef4 output (test-pinned).

Studio wiring: openExhibitStructureDirIfExists (store.ts, non-creating probe — a
publish never grows an empty structure/ dir) + a getStructure closure passed to all
three publish call sites (memory projection / zip / folder), tolerant-read with a
corrupt-page advisory (rule #2).

Proof: two-author end-to-end round trip (A publishes zip with structure -> B with a
concurrent edit imports via openArchieLibrary + replaceProjectFrom -> 2 heads on the
touched section, projected conflicted) + viewer-tolerance pin (published tree with
structure renders identical to one without).
micahchoo added a commit that referenced this pull request Jul 19, 2026
…ingest blocker (scale/ingest-batch)

Every imported object drove one whole-library.json rewrite (JSON.stringify of the
entire library, plus a read-first snapshotIfUnparseable) — a 100×100 collection
ingest paid ~10,000 sequential full-file rewrites, O(N²) cumulative bytes. This
was the #1 blocker for large ingests.

library-meta.svelte.ts:
- Add appendObjects(slug, objects[]) — the bulk sibling of appendObject, modeled
  on removeObjects/patchExhibits: fold N appends through appendObjmectIn into ONE
  meta mutation + ONE awaited persist + ONE `exhibit-assets` dirt. Empty batch is a
  no-op (identity preserved). No signalLibraryChanged (object-append isn't a
  library-STRUCTURE change), matching the singular appendObject.

ingest-flows.ts:
- Add a durable-append batch (beginBatch) with reserveId/add/flushIfFull/flush.
  Media byte-writes and seedMaster stay per object; only the library.json append
  coalesces. The view steers to the landed tail only AFTER its chunk is durable, so
  `current` never points at an object not yet in the reactive meta. reserveId mints
  ids against a running set (the meta isn't mutated until a flush, so nextObjectId
  would otherwise repeat).
- Convert importManifestObjects (manifest + collection), addFiles, and
  newExhibitFromFolder to the batch.
- Chunking decision: persist every 25 objects, NOT one flush at the very end. A
  single end flush is O(N) bytes but a crash mid-import loses the whole import;
  chunking bounds crash-loss to <25 objects (≈ the per-object durability the old
  code paid O(N²) for) while cutting rewrites 25×. A typical IIIF manifest (≤25
  canvases) is thus exactly ONE persist — the collection path's "1 per manifest"
  target — and only a pathologically large single manifest takes several. Every
  batch flush is awaited before the flow returns/navigates (durable-before-navigate);
  progress (setImportStatus) still ticks per object.
- Failure containment: newExhibitFromManifest now catches an exhibit-create throw
  and surfaces it via ctx.alert (the fetch/parse failure channel) — no unhandled
  rejection. importManifestObjects still lets a flush throw propagate so the
  collection drain catches it as fatal (unchanged skip/fatal/createdSlugs semantics).

Tests: pin persist-count (100-canvas manifest → 4 appendObjects, 0 appendObject;
100-file drop → 4; 60 → [25,25,10]; unique ids across the batch) and the
containment behavior; add appendObjects store tests. asset-queue.test.ts's inline
IngestContext mock gains appendObjects (mechanical — forced by the new API).

Gates: apps/studio pnpm typecheck clean; vitest 658/658 green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant